<template>
  <text class="mxp-count-num" :style="[{ fontSize, fontWeight: bold ? 'bold' : 'normal', color }, cusStyle]">
    {{ data.displayValue }}
  </text>
</template>

<script lang="ts" setup>
  import { publicStyeProps } from '../mxp-tool/utils/public-props';
  import { type PropType, computed, onMounted, onUnmounted, shallowReactive, watch } from 'vue';
  import type { $AllColorStr, $SizeUnitStr } from '@/mxp-ui/mxp-tool/types/interface';
  defineOptions({
    name: 'mxp-count-to',
  });

  const props = defineProps({
    /** #是否强制保留几位小数 (如：2.00是否需要舍去.00) */
    forcedDecimals: { type: Boolean, default: true },
    /** 开始的数值，默认从0增长到某一个数 */
    startVal: { type: Number, default: 0 },
    /** 要滚动的目标数值，必须 */
    endVal: { type: Number, default: 0 },
    /** 滚动到目标数值的动画持续时间，单位为毫秒(ms) */
    duration: { type: Number, default: 1200 },
    /** 设置数值后是否自动开始滚动 */
    autoplay: { type: Boolean, default: true },
    /** 保留几位小数 */
    decimals: { type: [String, Number], default: 2 },
    /** 是否在即将到达目标数值的时候，使用缓慢滚动的效果 */
    useEasing: { type: Boolean, default: true },
    /** 字体颜色 */
    color: { type: String as PropType<$AllColorStr>, default: '#606266' },
    /** 字体大小 */
    fontSize: { type: String as PropType<$SizeUnitStr>, default: '24px' },
    /** 是否加粗字体 */
    bold: { type: Boolean, default: false },
    /** 千位分隔符，类似金额的分割(￥23,321.05中的",") */
    separator: { type: String, default: ',' },
    /** #显示的前缀 */
    prefix: { type: String, default: '' },
    /** #显示的后缀 */
    suffix: { type: String, default: '' },
    ...publicStyeProps,
  });

  const emits = defineEmits<{
    (e: 'end'): void;
    (e: 'start'): void;
    (e: 'stateChange', state: 1 | 2 | 3 | 4): void;
  }>();
  const data = shallowReactive({
    localStartVal: props.startVal as number,
    displayValue: formatNumber(props.startVal),
    printVal: props.startVal as number,
    paused: false, // 是否暂停
    localDuration: Number(props.duration),
    startTime: null as number | null, // 开始的时间
    timestamp: null as number | null, // 时间戳
    remaining: null as number | null, // 停留的时间
    rAF: null as NodeJS.Timeout | null,
    lastTime: 0, // 上一次的时间
  });
  /** 当前状态; 1代表未播放,2代表播放中,3代表暂停播放,4代表播放完成 */
  const state = computed<1 | 2 | 3 | 4>(() => {
    return String(props.startVal) === String(data.printVal) ? 1 : String(props.endVal) === String(data.printVal) ? 4 : data.paused ? 3 : 2;
  });

  watch(
    () => [props.startVal, props.endVal],
    () => {
      props.autoplay && start();
    }
  );

  watch(
    () => state.value,
    (_state) => {
      emits('stateChange', _state);
    }
  );

  onMounted(() => {
    props.autoplay && start();
  });

  onUnmounted(() => {
    data.rAF && myCancelAnimationFrame(data.rAF);
  });

  /**
   * 动画函数
   */
  function easingFn(t: number, b: number, c: number, d: number) {
    return (c * (-(2 ** ((-10 * t) / d)) + 1) * 1024) / 1023 + b;
  }

  /**
   * 因为部分平台不支持 requestAnimationFrame() 函数所以手动实现
   */
  function myRequestAnimationFrame(callback: (t: number) => void) {
    const currTime = new Date().getTime();
    // 为了使setTimteout的尽可能的接近每秒60帧的效果
    const timeToCall = Math.max(0, 16 - (currTime - data.lastTime));
    const id = setTimeout(() => {
      callback(currTime + timeToCall);
    }, timeToCall);
    data.lastTime = currTime + timeToCall;
    return id;
  }
  /** 清除函数 */
  function myCancelAnimationFrame(id: NodeJS.Timeout) {
    clearTimeout(id);
  }

  /** 开始滚动数字 */
  function start() {
    emits('start');
    data.localStartVal = props.startVal;
    data.startTime = null;
    data.localDuration = props.duration;
    data.paused = false;
    data.rAF = myRequestAnimationFrame(count);
  }

  /** 暂停 */
  function pause() {
    data.rAF && myCancelAnimationFrame(data.rAF);
    data.paused = true;
  }

  /** 继续(暂停的情况下) */
  function resume() {
    if (!data.remaining) {
      return;
    }
    data.startTime = 0;
    data.localDuration = data.remaining;
    data.localStartVal = data.printVal;
    myRequestAnimationFrame(count);
  }

  /** 当为停状态下时继续滚动；当为播放状态下则暂停 */
  function restart() {
    if (data.paused) {
      resume();
      data.paused = false;
    } else {
      pause();
      data.paused = true;
    }
  }

  /** 重置 */
  function reset() {
    data.printVal = props.startVal;
    data.startTime = null;
    data.rAF && myCancelAnimationFrame(data.rAF);
    data.displayValue = formatNumber(props.startVal);
  }
  /** 开始滚动播放 */
  function count(timestamp: number) {
    if (!data.startTime) {
      data.startTime = timestamp;
    }
    data.timestamp = timestamp;
    const progress = timestamp - data.startTime;
    data.remaining = data.localDuration - progress;
    const countDown = props.startVal > props.endVal;
    if (props.useEasing) {
      if (countDown) {
        data.printVal = data.localStartVal - easingFn(progress, 0, data.localStartVal - props.endVal, data.localDuration);
      } else {
        data.printVal = easingFn(progress, data.localStartVal, props.endVal - data.localStartVal, data.localDuration);
      }
    } else {
      if (countDown) {
        data.printVal = data.localStartVal - (data.localStartVal - props.endVal) * (progress / data.localDuration);
      } else {
        data.printVal = data.localStartVal + (props.endVal - data.localStartVal) * (progress / data.localDuration);
      }
    }
    if (countDown) {
      data.printVal = data.printVal < props.endVal ? props.endVal : data.printVal;
    } else {
      data.printVal = data.printVal > props.endVal ? props.endVal : data.printVal;
    }
    data.displayValue = formatNumber(data.printVal) || '0';
    if (progress < data.localDuration) {
      data.rAF = myRequestAnimationFrame(count);
    } else {
      emits('end');
    }
  }
  /** 格式化 */
  function formatNumber(numb: number) {
    const newNumb = numb.toFixed(Number(props.decimals));
    // 把数字分割为整数和小数部分
    const x = newNumb.split('.');
    // 整数部分
    let x1 = x[0] || '';
    // 小数部分(包含小数点)，没有小数部分这为孔字符串
    let x2 = Number(x[1] || '') === 0 && !props.forcedDecimals ? '' : `.${x[1] || ''}`;
    // 如果没有强制保留几位小数，且结束值为整数那么就舍去小数部分
    x2 = !props.forcedDecimals && props.endVal % 1 === 0 ? '' : x2;
    // 设置千分位分隔符
    const rgx = /(\d+)(\d{3})/;
    if (x1 && props.separator && isNaN(Number(props.separator))) {
      while (rgx.test(x1)) {
        x1 = x1.replace(rgx, `$1${props.separator}$2`);
      }
    }
    return `${props.prefix}${x1}${x2}${props.suffix}`;
  }

  defineExpose({
    /** 播放 */
    play: start,
    /** 暂停 */
    pause,
    /** 继续 */
    resume,
    /** 重置 */
    reset,
    /** 当为停状态下时继续滚动；当为播放状态下则暂停 */
    restart,
    /** 得到状态; 1代表未播放,2代表播放中,3代表暂停播放,4代表播放完成 */
    getState(): 1 | 2 | 3 | 4 {
      return state.value;
    },
  });
</script>

<style lang="scss" scoped>
  .mxp-count-num {
    /* #ifndef APP-NVUE */
    display: inline-flex;
    /* #endif */
    text-align: center;
  }
</style>
